package controllers

import (
	"github.com/kataras/iris"
	"go_oauth2/constant"
	"go_oauth2/models"
	"go_oauth2/store"
	"go_oauth2/utils"
	"time"
)

type LoginRequestJson struct {
	Username  string `json:"username" validate:"required"`
	Password  string `json:"password" validate:"required"`
	GrantType string `json:"grant_type" validate:"required"`
	Scope     string `json:"scope" validate:"required"`
}

type RefreshTokenRequestJson struct {
	AccessToken  string `json:"access_token" validate:"required"`
	GrantType    string `json:"grant_type" validate:"required"`
	RefreshToken string `json:"refresh_token" validate:"required"`
}

type LoginResponseJson struct {
	Username     string `json:"username"`
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	Scope        string `json:"scope"`
	RefreshToken string `json:"refresh_token"`
}

/**
 * 登录获取Token（可以任意次数重复登录以刷新AccessToken和RefreshToken）
 * @method AccessToken
 * @param [iris.Context] context [IRIS上下文]
 */
func AccessToken(context iris.Context) {
	// 校验客户端配置信息是否符合标准
	isChecked, clientId := CheckRequestClient(context)
	if !isChecked {
		return
	}

	// 格式化输入信息
	loginRequest := new(LoginRequestJson)
	if err := context.ReadJSON(&loginRequest); err != nil {
		// 错误的请求
		context.StatusCode(iris.StatusBadRequest)
		// 请求参数错误，不能格式化成结构体
		_, _ = context.JSON(ApiResourceError(constant.RequestBodyError))
		return
	}

	// 校验结构体参数是否符合规则
	if err := validate.Struct(loginRequest); err != nil {
		// 错误的请求
		context.StatusCode(iris.StatusBadRequest)
		// 参数不符合规范
		_, _ = context.JSON(ApiResourceError(validatorErrorData(err)))
		return
	}

	// 不校验当前客户端的用户是否登录过
	// 不校验当前客户端的用户登录信息是否过期

	// 最后校验用户身份，即账号和密码是否符合
	if checkLogin := models.UserCheckLogin(loginRequest.Username, loginRequest.Password); !checkLogin {
		// 授权错误。当出现StatusUnauthorized时都应该重新登录
		context.StatusCode(iris.StatusUnauthorized)
		// 账号或者密码错误
		_, _ = context.JSON(ApiResourceError(constant.UsernameOrPasswordError))
		return
	}

	// 生成授权信息
	authToken := grantToken(loginRequest, nil, clientId)

	// 保存授权信息
	session := store.GetStore()
	if err := session.Save(authToken.Token, models.OAuth2Bytes(authToken)); err != nil {
		// 程序内部错误
		context.StatusCode(iris.StatusInternalServerError)
		// 程序内部意外的错误
		_, _ = context.JSON(ApiResourceError(constant.ProgramError))
		return
	}

	// 生成返回给前端的授权信息
	tokenResponse := grantReturnToken(authToken)
	// 返回给前端授权信息
	context.StatusCode(iris.StatusOK)
	_, _ = context.JSON(tokenResponse)
	return
}

/**
 * 根据RefreshToken刷新AccessToken
 * 1、若access_token已超时，那么进行refresh_token会获取一个新的access_token，新的超时时间；
 * 2、若access_token未超时，那么进行refresh_token不会改变access_token，但超时时间会刷新，相当于续期access_token
 * 3、refresh_token在执行刷新之后是不会变的
 * @method RefreshToken
 * @param [iris.Context] context [IRIS上下文]
 */
func RefreshToken(context iris.Context) {

	// 校验客户端配置信息是否符合标准
	isChecked, clientId := CheckRequestClient(context)
	if !isChecked {
		return
	}

	// 格式化输入信息
	refreshTokenRequest := new(RefreshTokenRequestJson)
	if err := context.ReadJSON(&refreshTokenRequest); err != nil {
		// 错误的请求
		context.StatusCode(iris.StatusBadRequest)
		// 请求参数错误，不能格式化成结构体
		_, _ = context.JSON(ApiResourceError(constant.RequestBodyError))
		return
	}

	// 校验结构体参数是否符合规则
	if err := validate.Struct(refreshTokenRequest); err != nil {
		// 错误的请求
		context.StatusCode(iris.StatusBadRequest)
		// 参数不符合规范
		_, _ = context.JSON(ApiResourceError(validatorErrorData(err)))
		return
	}

	// 判断grant_type是否符合规则
	if refreshTokenRequest.GrantType != constant.RefreshToken {
		// 错误的请求
		context.StatusCode(iris.StatusBadRequest)
		// 参数不符合规范
		_, _ = context.JSON(ApiResourceError(constant.RequestBodyError))
		return
	}

	// 校验当前客户端的用户是否登录过
	// 校验当前客户端的用户登录信息是否过期
	session := store.GetStore()
	tokenInfo, isExist, err := session.Find(refreshTokenRequest.AccessToken)
	if !isExist || err != nil {
		// 授权错误。当出现StatusUnauthorized时都应该重新登录
		context.StatusCode(iris.StatusUnauthorized)
		// 账号没有授权信息，无法刷新token
		_, _ = context.JSON(ApiResourceError(constant.UserAuthorizationError))
		return
	}

	lastOAuthToken := models.Bytes2OAuth(tokenInfo)

	// 比较refresh_token是否一致
	if lastOAuthToken.RefreshToken != refreshTokenRequest.RefreshToken {
		// 授权错误。当出现StatusUnauthorized时都应该重新登录
		context.StatusCode(iris.StatusUnauthorized)
		// 账号没有授权信息，无法刷新token
		_, _ = context.JSON(ApiResourceError(constant.UserAuthorizationError))
		return
	}

	// 校验refresh_token是否过期
	expirationTimeString := lastOAuthToken.Expiration
	expirationTime, err := time.Parse(constant.TimeFormat, expirationTimeString)
	if err != nil {
		// 程序内部错误
		context.StatusCode(iris.StatusInternalServerError)
		// 程序内部意外的错误
		_, _ = context.JSON(ApiResourceError(constant.ProgramError))
		return
	}

	// 因为access_token过期时间是2个小时，认为不超过1天
	// 所以直接加上refresh_token失效时间
	expTime := expirationTime.Unix() + constant.RefreshTokenDefaultTimeout
	refreshTokenExpirationTime := time.Unix(expTime, 0)
	if time.Now().After(refreshTokenExpirationTime) {
		// 授权错误。当出现StatusUnauthorized时都应该重新登录
		context.StatusCode(iris.StatusUnauthorized)
		// 账号没有授权信息，无法刷新token
		_, _ = context.JSON(ApiResourceError(constant.RefreshTokenTimeoutError))
		return
	}

	// 判断access_token是否过期
	authToken := grantToken(nil, lastOAuthToken, clientId)
	if time.Now().After(expirationTime) {
		// access_token已经过期，需要重新生成
		// 新的access_token的过期时间
		expTime := time.Now().Unix() + constant.TokenDefaultTimeout
		t := time.Unix(expTime, 0)
		date := t.Format(constant.TimeFormat)
		authToken.Token = utils.GetToken(clientId, lastOAuthToken.Username, date)
	} else {
		authToken.Token = lastOAuthToken.Token
	}

	// 保存授权信息
	if err := session.Save(authToken.Token, models.OAuth2Bytes(authToken)); err != nil {
		// 程序内部错误
		context.StatusCode(iris.StatusInternalServerError)
		// 程序内部意外的错误
		_, _ = context.JSON(ApiResourceError(constant.ProgramError))
		return
	}

	// 生成返回给前端的授权信息
	tokenResponse := grantReturnToken(authToken)
	// 返回给前端授权信息
	context.StatusCode(iris.StatusOK)
	_, _ = context.JSON(tokenResponse)
	return
}

/**
 * 生成&保存认证的Token
 * @method grantToken
 * @param [LoginResponseJson] tokenResponse [返回给客户端的Token信息]
 * @param [models.OAuthToken] refreshTokenOAuthToken [刷新Token信息]
 * @param [string] clientId [客户端ID]
 */
func grantToken(loginRequest *LoginRequestJson, refreshTokenOAuthToken *models.OAuthToken, clientId string) *models.OAuthToken {
	// access_token的过期时间
	expTime := time.Now().Unix() + constant.TokenDefaultTimeout
	t := time.Unix(expTime, 0)
	date := t.Format(constant.TimeFormat)
	// refresh_token的过期时间
	refreshExpTime := time.Now().Unix() + constant.RefreshTokenDefaultTimeout
	refreshTime := time.Unix(refreshExpTime, 0)
	refreshDate := refreshTime.Format(constant.TimeFormat)
	var tokenObject = new(models.OAuthToken)
	tokenObject.ClientId = clientId
	tokenObject.TokenType = constant.TokenDefaultType
	tokenObject.Expiration = date
	if loginRequest != nil && refreshTokenOAuthToken == nil {
		tokenObject.Username = loginRequest.Username
		tokenObject.Token = utils.GetToken(clientId, loginRequest.Username, date)
		tokenObject.TokenScope = loginRequest.Scope
		tokenObject.RefreshToken = utils.GetToken(clientId, loginRequest.Username, refreshDate)
	} else if loginRequest == nil && refreshTokenOAuthToken != nil {
		tokenObject.Username = refreshTokenOAuthToken.Username
		// 这一步会根据需求不同决定加不加
		// tokenObject.Token = uuid.Must(uuid.NewV4()).String()
		tokenObject.TokenScope = refreshTokenOAuthToken.TokenScope
		tokenObject.RefreshToken = refreshTokenOAuthToken.RefreshToken
	}
	return tokenObject
}

/**
 * 返回给客户端Token
 * @method returnToken
 * @param [models.OAuthToken] token [保存的Token对象]
 */
func grantReturnToken(token *models.OAuthToken) *LoginResponseJson {
	var tokenResponse = new(LoginResponseJson)
	tokenResponse.Username = token.Username
	tokenResponse.AccessToken = token.Token
	tokenResponse.TokenType = token.TokenType
	tokenResponse.ExpiresIn = constant.TokenDefaultTimeout
	tokenResponse.Scope = token.TokenScope
	tokenResponse.RefreshToken = token.RefreshToken
	return tokenResponse
}
